#include "ReShade.fxh"

uniform float display_sizeX <
	ui_type = "drag";
	ui_min = 1.0;
	ui_max = BUFFER_WIDTH;
	ui_label = "Screen Width (NTSC)";
> = 320.0;

uniform float display_sizeY <
	ui_type = "drag";
	ui_min = 1.0;
	ui_max = BUFFER_HEIGHT;
	ui_label = "Screen Height (NTSC)";
> = 240.0;

uniform bool bUseTwoPhase <
	ui_label = "Use Two-Phase (NTSC)";
> = false;

uniform bool bUseSVideo <
	ui_label = "Use S-Video (NTSC)";
	ui_tooltip = "Uses S-Video Cables instead of Composite.";
> = false;

uniform float NTSC_DISPLAY_GAMMA <
	ui_type = "drag";
	ui_min = 1.0;
	ui_max = 10.0;
	ui_step = 0.1;
	ui_label = "NTSC Display Gamma (NTSC)";
> = 2.1;

uniform float NTSC_CRT_GAMMA <
	ui_type = "drag";
	ui_min = 1.0;
	ui_max = 10.0;
	ui_step = 0.1;
	ui_label = "NTSC CRT Gamma (NTSC)";
> = 2.5;

#define PI 3.14159265

#define CHROMA_MOD_FREQ (PI / 3.0)
#define CHROMA_MOD_FREQ_TWO (4.0 * PI / 15.0)

#define SATURATION 1.0
#define BRIGHTNESS 1.0
#define ARTIFACTING 1.0
#define FRINGING 1.0

#define ARTIFACTING_SV 0.0
#define FRINGING_SV 0.0

#define display_size int2(display_sizeX,display_sizeY)

static const float3x3 mix_mat = float3x3(
      BRIGHTNESS, ARTIFACTING, ARTIFACTING,
      FRINGING, 2.0 * SATURATION, 0.0,
      FRINGING, 0.0, 2.0 * SATURATION 
);

static const float3x3 mix_mat_sv = float3x3(
      BRIGHTNESS, ARTIFACTING_SV, ARTIFACTING_SV,
      FRINGING_SV, 2.0 * SATURATION, 0.0,
      FRINGING_SV, 0.0, 2.0 * SATURATION 
);

static const float3x3 yiq2rgb_mat = float3x3(
   1.0, 0.956, 0.6210,
   1.0, -0.2720, -0.6474,
   1.0, -1.1060, 1.7046);

float3 yiq2rgb(float3 yiq)
{
   return mul(yiq2rgb_mat, yiq);
}

static const float3x3 yiq_mat = float3x3(
      0.2989, 0.5870, 0.1140,
      0.5959, -0.2744, -0.3216,
      0.2115, -0.5229, 0.3114
);

float3 rgb2yiq(float3 col)
{
   return mul(yiq_mat, col);
};

float fmod(float a, float b) {
    float c = frac(abs(a / b)) * abs(b);
    return (a < 0) ? -c : c;
};

texture target0_ntsc
{
    Width = BUFFER_WIDTH;
    Height = BUFFER_HEIGHT;
	Format = RGBA32F;
};
sampler s0 { Texture = target0_ntsc; };

uniform int framecount < source = "framecount"; >;

float4 NTSCStartPS( float4 pos : SV_Position, float2 texcoord : TEXCOORD0) : SV_Target
{
	return tex2D(s0,texcoord);
}

float4 NTSCEncodePS( float4 pos : SV_Position, float2 texcoord : TEXCOORD0) : SV_Target
{
	float3 col = tex2D(ReShade::BackBuffer, texcoord).rgb;
	float3 yiq = rgb2yiq(col);
	
	float2 pix_no = texcoord * display_size * float2( 4.0, 1.0 );
	float2 one = 1.0 / ReShade::ScreenSize;
	
	float chroma_phase = 0.6667 * PI * (fmod(pix_no.y, 3.0) + framecount);
	float mod_phase = chroma_phase + pix_no.x * CHROMA_MOD_FREQ;
	
	if (bUseTwoPhase){
		chroma_phase = PI * (fmod(pix_no.y, 2.0) + framecount);
		mod_phase = chroma_phase + pix_no.x * CHROMA_MOD_FREQ_TWO;
	}

	float i_mod = cos(mod_phase);
	float q_mod = sin(mod_phase);
	
	if(bUseSVideo){
		yiq.yz *= float2(i_mod, q_mod); // Modulate.
		yiq = mul(mix_mat_sv,yiq); // Cross-talk.
		yiq.yz *= float2(i_mod, q_mod); // Demodulate.
	} else {
		yiq.yz *= float2(i_mod, q_mod); // Modulate.
		yiq = mul(mix_mat,yiq); // Cross-talk.
		yiq.yz *= float2(i_mod, q_mod); // Demodulate.
	}
	
	return float4(yiq, 1.0);
}

float3 t2d(float2 texcoord)
{
	return tex2D(s0,texcoord);
}

#define fixCoord (texcoord - float2( 0.5 * one_x, 0.0)) 
#define fetch_offset(offset, one_x) t2d(fixCoord + float2( (offset) * (one_x), 0.0))

float4 NTSCDecodePS( float4 pos : SV_Position, float2 texcoord : TEXCOORD0) : SV_Target
{
	int TAPS = 24;
	
	float luma_filter[33] = {
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000
	};
	
	float chroma_filter[33] = {
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000,
		0.000000000
	};
	
	luma_filter[0] =	-0.000012020;
	luma_filter[1] =	-0.000022146;
	luma_filter[2] =	-0.000013155;
	luma_filter[3] =	-0.000012020;
	luma_filter[4] =	-0.000049979;
	luma_filter[5] =	-0.000113940;
	luma_filter[6] =	-0.000122150;
	luma_filter[7] =	-0.000005612;
	luma_filter[8] =	0.000170516;
	luma_filter[9] =	0.000237199;
	luma_filter[10] =	0.000169640;
	luma_filter[11] =	0.000285688;
	luma_filter[12] =	0.000984574;
	luma_filter[13] =	0.002018683;
	luma_filter[14] =	0.002002275;
	luma_filter[15] =	-0.000909882;
	luma_filter[16] =	-0.007049081;
	luma_filter[17] =	-0.013222860;
	luma_filter[18] =	-0.012606931;
	luma_filter[19] =	0.002460860;
	luma_filter[20] =	0.035868225;
	luma_filter[21] =	0.084016453;
	luma_filter[22] =	0.135563500;
	luma_filter[23] =	0.175261268;
	luma_filter[24] =	0.190176552;

	chroma_filter[0] =	-0.000118847;
	chroma_filter[1] =	-0.000271306;
	chroma_filter[2] =	-0.000502642;
	chroma_filter[3] =	-0.000930833;
	chroma_filter[4] =	-0.001451013;
	chroma_filter[5] =	-0.002064744;
	chroma_filter[6] =	-0.002700432;
	chroma_filter[7] =	-0.003241276;
	chroma_filter[8] =	-0.003524948;
	chroma_filter[9] =	-0.003350284;
	chroma_filter[10] =	-0.002491729;
	chroma_filter[11] =	-0.000721149;
	chroma_filter[12] =	0.002164659;
	chroma_filter[13] =	0.006313635;
	chroma_filter[14] =	0.011789103;
	chroma_filter[15] =	0.018545660;
	chroma_filter[16] =	0.026414396;
	chroma_filter[17] =	0.035100710;
	chroma_filter[18] =	0.044196567;
	chroma_filter[19] =	0.053207202;
	chroma_filter[20] =	0.061590275;
	chroma_filter[21] =	0.068803602;
	chroma_filter[22] =	0.074356193;
	chroma_filter[23] =	0.077856564;
	chroma_filter[24] =	0.079052396;
	
	if (bUseTwoPhase){
		
		TAPS = 32;
	
		luma_filter[0] =	-0.000205844;
		luma_filter[1] =	-0.000149453;
		luma_filter[2] =	-0.000051693;
		luma_filter[3] =	0.000000000;
		luma_filter[4] =	-0.000066171;
		luma_filter[5] =	-0.000245058;
		luma_filter[6] =	-0.000432928;
		luma_filter[7] =	-0.000472644;
		luma_filter[8] =	-0.000252236;
		luma_filter[9] =	0.000198929;
		luma_filter[10] =	0.000687058;
		luma_filter[11] =	0.000944112;
		luma_filter[12] =	0.000803467;
		luma_filter[13] =	0.000363199;
		luma_filter[14] =	0.000013422;
		luma_filter[15] =	0.000253402;
		luma_filter[16] =	0.001339461;
		luma_filter[17] =	0.002932972;
		luma_filter[18] =	0.003983485;
		luma_filter[19] =	0.003026683;
		luma_filter[20] =	-0.001102056;
		luma_filter[21] =	-0.008373026;
		luma_filter[22] =	-0.016897700;
		luma_filter[23] =	-0.022914480;
		luma_filter[24] =	-0.021642347;
		luma_filter[25] =	-0.008863273;
		luma_filter[26] =	0.017271957;
		luma_filter[27] =	0.054921920;
		luma_filter[28] =	0.098342579;
		luma_filter[29] =	0.139044281;
		luma_filter[30] =	0.168055832;
		luma_filter[31] =	0.178571429;
			
		chroma_filter[0] =	0.001384762;
		chroma_filter[1] =	0.001678312;
		chroma_filter[2] =	0.002021715;
		chroma_filter[3] =	0.002420562;
		chroma_filter[4] =	0.002880460;
		chroma_filter[5] =	0.003406879;
		chroma_filter[6] =	0.004004985;
		chroma_filter[7] =	0.004679445; 
		chroma_filter[8] =	0.005434218;
		chroma_filter[9] =	0.006272332; 
		chroma_filter[10] =	0.007195654; 
		chroma_filter[11] =	0.008204665; 
		chroma_filter[12] =	0.009298238; 
		chroma_filter[13] =	0.010473450;
		chroma_filter[14] =	0.011725413; 
		chroma_filter[15] =	0.013047155;
		chroma_filter[16] =	0.014429548;
		chroma_filter[17] =	0.015861306; 
		chroma_filter[18] =	0.017329037; 
		chroma_filter[19] =	0.018817382;
		chroma_filter[20] =	0.020309220; 
		chroma_filter[21] =	0.021785952;
		chroma_filter[22] =	0.023227857;
		chroma_filter[23] =	0.024614500; 
		chroma_filter[24] =	0.025925203;
		chroma_filter[25] =	0.027139546; 
		chroma_filter[26] =	0.028237893;
		chroma_filter[27] =	0.029201910; 
		chroma_filter[28] =	0.030015081; 
		chroma_filter[29] =	0.030663170;
		chroma_filter[30] =	0.031134640;
		chroma_filter[31] =	0.031420995; 
		chroma_filter[32] =	0.031517031;
	}
	
	float one_x = 1.0 / ReShade::ScreenSize.x;
	one_x *= 0.5;
	
	float3 signal = float3(0.0,0.0,0.0);
	for (int i = 0; i < TAPS; i++)
	{
	   float offset = float(i);

	   float3 sums = fetch_offset(offset - float(TAPS), one_x) +
		  fetch_offset(float(TAPS) - offset, one_x);

	   signal += sums * float3(luma_filter[i], chroma_filter[i], chroma_filter[i]);
	}
	signal += tex2D(s0, texcoord).xyz *
	   float3(luma_filter[TAPS], chroma_filter[TAPS], chroma_filter[TAPS]);
	   
   float3 rgb = yiq2rgb(signal);
   return float4(rgb, 1.0);
}

float4 NTSCGaussPS( float4 pos : SV_Position, float2 texcoord : TEXCOORD0) : SV_Target
{

	float2 pix_no = texcoord * display_size * float2( 4.0, 1.0 );
	float2 one = 1.0 / ReShade::ScreenSize;

   #define TEX(off) pow(tex2D(ReShade::BackBuffer, texcoord + float2(0.0, (off) * one.y)).rgb, float3(NTSC_CRT_GAMMA,NTSC_CRT_GAMMA,NTSC_CRT_GAMMA))

   float3 frame0 = TEX(-2.0);
   float3 frame1 = TEX(-1.0);
   float3 frame2 = TEX(0.0);
   float3 frame3 = TEX(1.0);
   float3 frame4 = TEX(2.0);

   float offset_dist = frac(pix_no.y) - 0.5;
   float dist0 =  2.0 + offset_dist;
   float dist1 =  1.0 + offset_dist;
   float dist2 =  0.0 + offset_dist;
   float dist3 = -1.0 + offset_dist;
   float dist4 = -2.0 + offset_dist;

   float3 scanline = frame0 * exp(-5.0 * dist0 * dist0);
   scanline += frame1 * exp(-5.0 * dist1 * dist1);
   scanline += frame2 * exp(-5.0 * dist2 * dist2);
   scanline += frame3 * exp(-5.0 * dist3 * dist3);
   scanline += frame4 * exp(-5.0 * dist4 * dist4);

   return float4(pow(1.15 * scanline, float3(1.0 / NTSC_DISPLAY_GAMMA, 1.0 / NTSC_DISPLAY_GAMMA, 1.0 / NTSC_DISPLAY_GAMMA)), 1.0);
}

float4 NTSCFinalPS( float4 pos : SV_Position, float2 texcoord : TEXCOORD0) : SV_Target
{
	return tex2D(ReShade::BackBuffer,texcoord);
}


technique NTSC_RetroArch
{	
	// first pass: encode s-video signal
	pass p1
	{	
		RenderTarget = target0_ntsc;
		
		VertexShader = PostProcessVS;
		PixelShader = NTSCEncodePS;
	}
	
	// second pass: decode composite signal
	pass p2
	{
		VertexShader = PostProcessVS;
		PixelShader = NTSCDecodePS;
	}
	// third pass: adds gaussian with scanlines
	pass p3
	{
		VertexShader = PostProcessVS;
		PixelShader = NTSCGaussPS;
	}
	// final pass: decode composite signal
	pass p4
	{
		VertexShader = PostProcessVS;
		PixelShader = NTSCFinalPS;
	}
}